package com.auth.manage.lock;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import org.springframework.data.mapping.model.SpELContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

@Slf4j
@RequiredArgsConstructor
@Aspect
public class DistributeAspect {
    private final RedissonClient redissonClient;

    @Around("@annotation(com.auth.manage.lock.DistributeLock)")
    public Object process(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 1.获取注解携带的各种锁信息
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = methodSignature.getMethod();
        DistributeLock annotation = method.getAnnotation(DistributeLock.class);
        int waitTime = annotation.waitTime();
        String key = annotation.key();
        int releaseTime = annotation.releaseTime();
        String expression = annotation.keyExpression();
        // 2.如果key和Expression都用的默认，则抛出异常，防止造成没必要的锁争抢
        if (DistributeLockConstant.DEFAULT_KEY.equals(key) && DistributeLockConstant.DEFAULT_KEY.equals(expression)){
            throw new DistributeLockException("unknown lock key");
        }
        // 3.根据SPEL表达式获取参数值
        // 3.1 参数值和参数名绑定
        Object[] args = proceedingJoinPoint.getArgs();
        StandardReflectionParameterNameDiscoverer discoverer = new StandardReflectionParameterNameDiscoverer();
        String[] parameterNames = discoverer.getParameterNames(method);
        StandardEvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], args[i]);
        }
        // 3.2 解析#+形参名对应的形参值
        SpelExpressionParser parser = new SpelExpressionParser();
        Expression parseExpression = parser.parseExpression(expression);
        Object expressionValue = parseExpression.getValue(context);  // Long无法直接转为String
        String value = String.valueOf(expressionValue);
        // 4.拼接key
        String lockKey = annotation.scene() + '#' + value;
        // 5.根据waitTime和releaseTime()执行不同的方法
        RLock lock = redissonClient.getLock(lockKey);
        Boolean flag;
        Object responce;
       try {
           if (waitTime == DistributeLockConstant.DEFAULT_WAIT_TIME){
               // 5.1 无限等待 + 锁无过期时间
               if (releaseTime == DistributeLockConstant.DEFAULT_WAIT_RELEASE_TIME) {
                   log.info("lock key:{}", lockKey);
                   lock.lock();
               }else {
                   // 5.2 无限等待 + 有过期时间
                   log.info("lock key:{},expireTime:{}",lockKey, releaseTime);
                   lock.lock(waitTime, TimeUnit.SECONDS);
               }
               flag = true;
           }else {
               // 5.3 有限等待 + 锁无过期时间 -- 看门狗
               if (releaseTime == DistributeLockConstant.DEFAULT_WAIT_RELEASE_TIME){
                   log.info("lock key:{},waitTime:{}",lockKey, waitTime);
                   flag = lock.tryLock(waitTime, TimeUnit.SECONDS);
               }else{
                   // 5.4 有限等待 + 有过期时间
                   log.info("lock key:{},waitTime:{},releaseTime:{}",lockKey, waitTime,releaseTime);
                   flag = lock.tryLock(waitTime, releaseTime, TimeUnit.SECONDS);
               }
           }
           // 6.如果没抢到锁，则抛出异常
           if (!flag) {  // 获取锁失败
               log.warn("获取分布式锁失败, key:{}, expire:{}, wait_time:{}", lockKey, releaseTime, waitTime);
               throw new DistributeLockException("lock fail");
           }
           responce = proceedingJoinPoint.proceed();
       }finally {
           // 7.释放锁
           lock.unlock();
       }
        return responce;
    }
}
